package com.yaoxun.gateway.service;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.yaoxun.gateway.config.GatewayProperties;
import com.yaoxun.gateway.constant.CacheConstant;
import com.yaoxun.gateway.util.JsonUtils;
import com.yaoxun.gateway.xo.dto.ChannelMsg;
import com.yaoxun.gateway.xo.dto.RouterDefinition;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Mono;

@Slf4j
public class RouteDefinitionService implements InitializingBean, ApplicationEventPublisherAware {

	@Autowired
	private RouteDefinitionRepository routeDefinitionRepository;

	private ApplicationEventPublisher applicationEventPublisher;
	
	@Autowired
	private StringRedisTemplate stringRedisTemplate;

	@Autowired
	private GatewayProperties gatewayProperties;
	
	private void toPredicates(RouteDefinition route, RouterDefinition router) {
		
		if(CollectionUtils.isEmpty(router.getPredicates())) {
			return ;
		}
		
		router.getPredicates().forEach(e -> {
			
			PredicateDefinition definition = new PredicateDefinition();
			definition.setName(e.getName());
			definition.setArgs(e.getArgs());
			
			route.getPredicates().add(definition);
			
		});
		
	}
	
	private void toFilters(RouteDefinition route, RouterDefinition router) {
		
		if(CollectionUtils.isEmpty(router.getFilters())) {
			return ;
		}
		
		router.getFilters().forEach(e -> {
			
			FilterDefinition definition = new FilterDefinition();
			definition.setName(e.getName());
			definition.setArgs(e.getArgs());
			
			route.getFilters().add(definition);
			
		});
		
	}
	
	private void toMetadata(RouteDefinition route, RouterDefinition router) {
		
		if(MapUtils.isEmpty(router.getMetadata())) {
			return ;
		}
		
		route.setMetadata(router.getMetadata());
		
	}
	
	private RouteDefinition toRoute(RouterDefinition router) {
		RouteDefinition route = new RouteDefinition();
		route.setId(router.getId());
		route.setOrder(router.getOrder());
		route.setUri(URI.create(router.getUri()));
		
		toPredicates(route, router);
		
		toFilters(route, router);
		
		toMetadata(route, router);
		
		return route;
	}
	
	
	public void add(ChannelMsg msg) {
		log.info("router add msg:{}", msg);
		
		RouterDefinition router = msg.getRouter();
		
		RouteDefinition route = toRoute(router);
		
		add(route);
		
	}

	
	public void update(ChannelMsg msg) {
		
		log.info("router update msg:{}", msg);
		
		RouterDefinition router = msg.getRouter();
		
		RouteDefinition route = toRoute(router);
		
		update(route);
		
	}
	
	
	public void delete(ChannelMsg msg) {
		log.info("router delete msg:{}", msg);
		
		String id = msg.getRouter().getId();
		
		delete(id);
		
	}
	
	public void update(RouteDefinition route) {
		String id = route.getId();
		routeDefinitionRepository.delete(Mono.just(id)).subscribe();
		routeDefinitionRepository.save(Mono.just(route)).subscribe();
		this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
	}
	
	
	public void delete(String id) {
        routeDefinitionRepository.delete(Mono.just(id)).subscribe();
        this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
	}
	
	public void deleteBatch(Collection<String> ids) {
		
		if(CollectionUtils.isEmpty(ids)) {
			return ;
		}
		
		for(String id : ids) {			
			routeDefinitionRepository.delete(Mono.just(id)).subscribe();
		}
		
		this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
	}
	
	public void add(RouteDefinition route) {
		routeDefinitionRepository.save(Mono.just(route)).subscribe();
		this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
	}
	
	public void addBatch(List<RouteDefinition> routes) {
		for(RouteDefinition route : routes) {
			routeDefinitionRepository.save(Mono.just(route)).subscribe();
		}
		this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
	}

	@Override
	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
		this.applicationEventPublisher = applicationEventPublisher;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		
		log.info("gateway load routers....");
		
		String routerKey = String.format(CacheConstant.ROUTERS_NAMESPACE, gatewayProperties.getNamespace());
		
		List<Object> values = stringRedisTemplate.opsForHash().values(routerKey);
		
		if(CollectionUtils.isEmpty(values)) {
			log.info("gateway load routers is empty....");
			return ;
		}
		
		log.info("gateway load routers values:{}", values);
		
		List<RouteDefinition> routes = new ArrayList<>();
		
		values.forEach(e -> {
			
			RouterDefinition router = JsonUtils.parse(e.toString(), RouterDefinition.class);
			
			RouteDefinition route = toRoute(router);
			
			routes.add(route);
			
		});
		
		addBatch(routes);
		
		log.info("gateway load routes success...");
		
	}
	
}
